package com.unionpay.atm.sdk;

import java.nio.charset.Charset;


/**
 * Implementation of Chinese SM3 digest algorithm
 */
public class SM3Digest{
	
    private static final int DIGEST_LENGTH = 32;            // sm3 digest length (bytes)
    private static final int MSG_PER_UNIT_LENGTH = 64;      // message unit to be processed by sm3 algorithm
    private int[] IV = new int[DIGEST_LENGTH / 4];          // intermediate digest state (8 words) 
    private int[] total = new int [2];                      // number of bytes processed
    private byte[] buffer = new byte[MSG_PER_UNIT_LENGTH];  // data block to be processed
    
    // extending message words used within function processBlock()
    private int[] W = new int[68];
    private int[] W1 = new int[64];

    // temporary constant
    private static final int[] T = new int[MSG_PER_UNIT_LENGTH];
    private final byte SM3PADDING[] = 
    {
    	(byte)0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    	         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    	         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    	         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };

    static
    {
        for (int i = 0; i < 16; ++i)
        {
            int t = 0x79CC4519;
            T[i] = (t << i) | (t >>> (32 - i));
        }
        for (int i = 16; i < 64; ++i)
        {
            int n = i % 32;
            int t = 0x7A879D8A;
            T[i] = (t << n) | (t >>> (32 - n));
        }
    }


    /**
     * Standard constructor
     */
    public SM3Digest()
    {
        reset();
    }
    
    /**
     * calculate sm3 digest
     * @param bArrInput: message to generate sm3 digest
     * @return byte array to store the generated sm3 digest
     */
    public byte[] calSM3Digest(byte[]bArrInput)
    {
    	if(0 == bArrInput.length)
    		return new byte[0];
    	
    	sm3Update(bArrInput, bArrInput.length);
    	byte[] bArrOutput = new byte[DIGEST_LENGTH];
    	sm3Finish(bArrOutput);
    	return bArrOutput;
    }
    
    /**
     * get integer corresponding to byte array
     * @param bArrData: byte array 
     * @param offset: offset of bArrData 
     * @param index: index of bArrData
     * @return integer value
     */
    private int getIntFromByteArray(final byte[]bArrData, int offset, int index)
    {
    	
    	return (((bArrData[offset+index]   & 0xff) << 24) |      
                ((bArrData[offset+index+1] & 0xff) << 16) |
                ((bArrData[offset+index+2] & 0xff) << 8)  |
                ((bArrData[offset+index+3] & 0xff)));
    }
    
    /**
     * get unsigned integer corresponding to integer
     * @param n: integer input
     * @return unsigned long value(i.e.unsigned integer)
     */
    private long getUnsignedInt(final int n)
    {
//    	System.out.printf("before(x):%x\n", n);
//    	System.out.println("n & 0xFFFFFFFF:"+ (n & 0xFFFFFFFF));
//    	System.out.printf("after(x):%d\n", n & 0xFFFFFFFF);
    	return n & 0xFFFFFFFF;
    }
    
    /**
     * get byte array corresponding to integer
     * @param bArrData: byte array to store the bytes
     * @param index: index of bArrData
     * @param n: integer 
     * @return void 
     */
    private void getBytesFromInt(byte[]bArrData, int index, final int n)
    {
    	bArrData[index] =   (byte)(0xFF & (n >> 24));
    	bArrData[index+1] = (byte)(0xFF & (n >> 16));
    	bArrData[index+2] = (byte)(0xFF & (n >> 8));
    	bArrData[index+3] = (byte)(0xFF & n);
    }
    
    /**
     * reset the chaining variables
     * @param nothing
     * @return void 
     */
    private void reset()
    {
        this.IV[0] = 0x7380166F; 
        this.IV[1] = 0x4914B2B9;
        this.IV[2] = 0x172442D7;
        this.IV[3] = 0xDA8A0600;
        this.IV[4] = 0xA96F30BC;
        this.IV[5] = 0x163138AA;
        this.IV[6] = 0xE38DEE4D;
        this.IV[7] = 0xB0FB0E4E;
    }
    
    /**
     * rotate left some bits
     * @param n: integer to be rotated
     * @param bit: rotate left bit bits 
     * @return the result of rotating left with bit bits 
     */
    private int rotateLeft(final int n, int bit)
    {
    	return ((n << bit) | (n >>> (32 - bit)));
    }
    
    /**
     * replacement function used for compressing message 
     * @param x: integer input
     * @return replacement value 
     */
    private int P0(final int x)
    {
        return (x ^ rotateLeft(x, 9) ^ rotateLeft(x, 17));
    }

    /**
     * replacement function used for extending message 
     * @param x: integer input
     * @return replacement value 
     */
    private int P1(final int x)
    {
    	return (x ^ rotateLeft(x, 15) ^ rotateLeft(x, 23));
    }

    /**
     * boolean function used for compressing message 
     * @param x: integer input
     * @param y: integer input
     * @param z: integer input
     * @return integer value 
     */
    private int FF0(final int x, final int y, final int z)
    {
        return (x ^ y ^ z);
    }

    /**
     * boolean function used for compressing message 
     * @param x: integer input
     * @param y: integer input
     * @param z: integer input
     * @return integer value  
     */
    private int FF1(final int x, final int y, final int z)
    {
        return ((x & y) | (x & z) | (y & z));
    }

    /**
     * boolean function used for compressing message 
     * @param x: integer input
     * @param y: integer input
     * @param z: integer input
     * @return integer value 
     */
    private int GG0(final int x, final int y, final int z)
    {
        return (x ^ y ^ z);
    }

    /**
     * boolean function used for compressing message 
     * @param x: integer input
     * @param y: integer input
     * @param z: integer input
     * @return integer value 
     */
    private int GG1(final int x, final int y, final int z)
    {
        return ((x & y) | ((~x) & z));
    }

    /**
     * compress the message iteratively
     * @param bArrData: message to be processed
     * @param offset: offset of bArrData
     * @return void 
     */
    private void processBlock(final byte[] bArrData, int offset)  //sm3_process
    {
    	// extend message: get w[0]...w[67], w1[0]...w1[63]
        for (int j = 0; j < 16; ++j) 
        {
            this.W[j] = getIntFromByteArray(bArrData,offset,j*4);
        }
        for (int j = 16; j < 68; ++j) 
        {
            int wj3 = this.W[j - 3];
            int r15 = rotateLeft(wj3, 15);
            int wj13 = this.W[j - 13];
            int r7 = rotateLeft(wj13, 7);
            this.W[j] = P1(this.W[j - 16] ^ this.W[j - 9] ^ r15) ^ r7 ^ this.W[j - 6];
        }
        for (int j = 0; j < 64; ++j) 
        {
            this.W1[j] = this.W[j] ^ this.W[j + 4];
        }

        // compress message with big-endian format
        int A = this.IV[0];
        int B = this.IV[1];
        int C = this.IV[2];
        int D = this.IV[3];
        int E = this.IV[4];
        int F = this.IV[5];
        int G = this.IV[6];
        int H = this.IV[7];

        for (int j = 0; j < 16; ++j)
        {
            int a12 = rotateLeft(A, 12);
            int SS1 = rotateLeft(a12 + E + T[j], 7);
            int SS2 = SS1 ^ a12;
            int TT1 = FF0(A, B, C) + D + SS2 + this.W1[j];
            int TT2 = GG0(E, F, G) + H + SS1 + this.W[j];
            D = C;
            C = rotateLeft(B, 9);
            B = A;
            A = TT1;
            H = G;
            G = rotateLeft(F, 19);
            F = E;
            E = P0(TT2);
        }

        // Different FF,GG functions on rounds 16...63
        for (int j = 16; j < 64; ++j)   
        {
        	int a12 = rotateLeft(A, 12);
        	int SS1 = rotateLeft(a12 + E + T[j], 7);
            int SS2 = SS1 ^ a12;
            int TT1 = FF1(A, B, C) + D + SS2 + this.W1[j];
            int TT2 = GG1(E, F, G) + H + SS1 + this.W[j];
            D = C;
            C = rotateLeft(B, 9);
            B = A;
            A = TT1;
            H = G;
            G = rotateLeft(F, 19);
            F = E;
            E = P0(TT2);
        }

        this.IV[0] ^= A;
        this.IV[1] ^= B;
        this.IV[2] ^= C;
        this.IV[3] ^= D;
        this.IV[4] ^= E;
        this.IV[5] ^= F;
        this.IV[6] ^= G;
        this.IV[7] ^= H;

    }
    
    /**
     * deal with the message according to 64 bytes per unit
     * @param bArrInput: message to be processed
     * @param iLen: the actual length of bArrInput 
     * @return void 
     * @exception IndexOutOfBoundsException
     */
    private void sm3Update(byte[]bArrInput, int iLen)
    {
    	if(iLen <= 0)
    		return;
    	int iLeft = this.total[0] & 0x3f;  // message length(bytes) remained to be processed
    	int iFill = MSG_PER_UNIT_LENGTH - iLeft; // padding message
    	this.total[0] += iLen;
    	this.total[0] &= 0xFFFFFFFF;
    	if(getUnsignedInt(this.total[0]) < getUnsignedInt(iLen))
    		this.total[1]++;
    	int iPos = 0;
    	if(iLeft != 0 && iLen >= iFill)
    	{
    		try{
    			System.arraycopy(bArrInput, 0, buffer, iLeft, iFill);
    		}catch(IndexOutOfBoundsException ex){
    			ex.printStackTrace();
    		}
    		processBlock(buffer, 0);
    		iPos += iFill;
    		iLen -= iFill;
    		iLeft = 0;
    	}
    	while(iLen >= MSG_PER_UNIT_LENGTH)
    	{
    		processBlock(bArrInput, iPos);
    		iPos += MSG_PER_UNIT_LENGTH;
    		iLen -= MSG_PER_UNIT_LENGTH;		
    	}
    	if( iLen > 0 )
    		try{
    			System.arraycopy(bArrInput, iPos, buffer, iLeft, iLen);
    		}catch(IndexOutOfBoundsException ex){
    			ex.printStackTrace();
    		}
    	
    }
    
    /**
     * deal with the remained message(less than 64 bytes), padding and the length of message part 
     * @param bArrInput: message remained
     * @return void 
     */
    private void sm3Finish(byte []bArrOutput)
    {
    	int iHigh = (this.total[0] >> 29) | (this.total[1] << 3);
    	int iLow = this.total[0] << 3;
    	byte []bArrMsgLen = new byte[8];
    	getBytesFromInt(bArrMsgLen, 0, iHigh);
    	getBytesFromInt(bArrMsgLen, 4, iLow);
    	int iLast = this.total[0] & 0x3F;
    	int iPadding = (iLast < 56) ? (56 -iLast) : (120-iLast);
    	sm3Update(this.SM3PADDING, iPadding); // process the padding part
    	sm3Update(bArrMsgLen, 8); // process the length of message part
    	for(int i = 0; i < 8; i++)
    		getBytesFromInt(bArrOutput, i*4, this.IV[i]);
    }


} // end of class SM3Digest


